iNOBStudios logo

Introducing badgestore.dev

Posted on 2023-06-08 | Last updated 2023-06-09 | Tags: ProgrammingWeb-Development

I have just released a new website: badgestore.dev. The aim is to have a place where you can store arbitrary badges, and update them programatically.

I previously had a similar project: LOCCounterBadge, in which you could provide a repo, and it would count the lines of code, and display it out as a badge. Give it a webhook and it would keep updating the badge as you pushed code.
However said project was quite cumbersome to use. You would need to set up a deploy key (if it was private), and a webhook for each of your projects.

badgestore.dev solves this problem by providing badges generally instead of tied to a repo. This allows you to set up a CI pipeline like github actions to count your lines of code (or other task), which sends a PUT request to the API, updating the badge.

Motivation

There are great services out there like shields.io that allow you to create badges. They even provide custom ones like code coverage and package download count. I think this works by shields scanning your repo to provide the badge. But the problem for me is that most of my projects are (at least initially) private, so shields.io, despite being a great service, is not able to scan my repo to create the badge.

How it works

name: Count lines of code for the project, and upload to the badge store

on:
  push:
    branches:
      - 'master'

jobs:
  count-loc-and-upload:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v3
      - id: loc
        name: Count lines of code
        uses: Sh1nku/count-loc-action@v1
        with:
          excluded: "*.json,*.svg,*.md"
      - uses: Sh1nku/badgestore-update-badge-action@v1
        name: Update badge
        id: badge
        with:
          right-label: ${{ steps.loc.outputs.Total_code }}
          read-write-key: ${{ secrets.LOC_COUNT_BADGE_RW_KEY }}
      - uses: koki-develop/hub-purge-action@v1
The above is a simple github action in use on this repository which produces a badge like this

Since the github action is run on a push to master, the badge contents get auto updated.

Implementation

I decided to write the back-end in Rust with Axum, the most popular web framework at the moment. Personally, out of all the big Rust back-end frameworks I feel Axum is the , but it still feels clunky to use, so I hope a simpler one comes out soon.
I tried setting up Leptos as the front-end, to complete the full stack in Rust, but since I am a shoddy front-end developer to begin with, and therefore need a lot of examples and documentation to do simple stuff, I decided to fall back on the good old reliable Sveltekit (this time with Typescript and Tailwind).

Speaking of Rust; Writing Rust is a dream. I feel it both fits and is the best choice for all parts of the back-end stack. Whether writing a performance-critical game engine, a back-end web-service or a distributed system, Rust is in my mind the best option.
I used to prefer C++ for performance critical code, C# with ASP.Net Core for web-services and Python for small cli tools, but they can all be replaced by Rust (once it gets an as good back-end web framework as ASP.Net Core at least).
Rust also ruined all other programming languages for me. I get agitated by how many things Rust would have caught at compile time, and forced me to handle, instead of giving runtime errors.
Maybe at some point I will write an article about how great Rust is.

Back to the topic at hand. The infrastructure is simple. A badge has four fields, the left label, the right-label, a left color, and a right color. If you use it in conjunction with shields.io, you can get a logo as well, although I did not want to support it for the internal badge generation.
The database stores said four fields, along with a read key, and an encrypted write key; Using those you can update the badge using the API.
You have the option to use either badgestore.dev, shields.io, or badgen.net to generate the badge, the other two offering an endpoint that takes the URL of an endpoint returning the correct URL.

Challenges

There were a few things I felt were important during the implementation.

How to avoid misuse

I have basically provided a service where people can store arbitrary data, so misuse could occur. If I had no safeguards, you would be able to generate unlimited badges, causing my poor database to fill up. The only real way to mitigate this without forcing users to authenticate, is to implement rate limiting. I have a hard limit Per-IP of 100 badges per day. In addition I tried being as conservative as possible with how much data each badge could store, ending up on 96 bytes for the left-label, and the same for the right-label. In total, each badge takes up 372 bytes of storage. This means that 1 million badges (a lot more than what I would think the service needs) comes to 372 MB of storage.

Authentication

Smart people tell you to avoid storing passwords if at all possible, but here it seems unavoidable. You need a password to authenticate that a badge is yours. Pub-key authentication would also have worked, but I went for the simplest solution I could think of.
When generating a badge, you get an 8 byte read_key formatted as hexadecimal, eg: 6972b9a3fc2deb64, and a 20 byte write-key. The write key is encrypted using Argon2id with a 32 byte salt.
Maybe I made a mistake, and this is easily cracked, or maybe the way I generate these two keys in the first place is flawed. I really don't like thinking about stuff like this It's a lot simpler having a full framework deal with user authentication for you. Luckily, this service is not meant to store sensitive, or user-identifying data, so even if it is flawed, the consequences are minimal.

Conclusion

This was a nice little project, if someone starts using it I would be very happy. I personally have started filling my github repos with lines of code badges, and mulling on what other uses it could have.
If you like the project, consider giving it a star on github. It would mean a lot to me.